use core::{ops::{Deref, DerefMut}, slice};
use periph_utils::flash;
use utils::ufmt;

pub trait Reset {
    
    fn reset(&mut self);
}

pub struct Store<T: Copy + Reset, const FLASH_ADDR: u32> {
    pub value: T,
    md5_sum: [u8; 16],
}

impl <T: Copy + Reset, const FLASH_ADDR: u32> Deref for Store<T, FLASH_ADDR> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl <T: Copy + Reset, const FLASH_ADDR: u32> DerefMut for Store<T, FLASH_ADDR> {

    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.value
    }
}


#[derive(Debug, Clone, Copy, ufmt::derive::uDebug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
    Md5,
    Flash(flash::Error),
}

impl <T: Copy + Reset, const FLASH_ADDR: u32> Store<T, FLASH_ADDR> {

    pub const fn const_new(value: T) -> Self {
        Self { value, md5_sum: [0; 16] }
    }

    pub fn load(&mut self) -> Result<(), Error> {
        let ptr = self as *const Self as *mut u8;
        flash::read(FLASH_ADDR, ptr, size_of::<Self>());
        if self.verify() {
            Ok(())
        } else {
            self.reset();
            Err(Error::Md5)
        }
    }

    pub fn store(&mut self) -> Result<(), Error> {
        self.md5_sum = self.calc_md5();
        flash::erase(FLASH_ADDR, size_of::<Self>()).map_err(Error::Flash)?;
        let ptr = self as *const Self as *const u8;
        flash::write(FLASH_ADDR, ptr, size_of::<Self>()).map_err(Error::Flash)?;
        Ok(())
    }

    fn verify(&self) -> bool {
        let sum = self.calc_md5();
        sum.as_ref() == &self.md5_sum
    }

    fn calc_md5(&self) -> [u8; 16] {
        let ptr = (&self.value) as *const T as *const u8;
        let data = unsafe { slice::from_raw_parts(ptr, size_of::<T>()) };
        let sum = md5::compute(data);
        sum.0
    }
}







